/*
 *  Copyright (c) 2009, Hugo Augusto Alves
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *      * Neither the name of the Hugo Augusto Alves nor the
 *        names of its contributors may be used to endorse or promote products
 *        derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.wiscriptor.lmldocument;

import java.util.ArrayList;
import java.util.Date;
import org.wiscriptor.utils.Utils;

/**
 * Lml Document Format
 * A lightweight markup language
 *
 * Formats supported to export:
 * - txt
 * - html
 *
 * ~TODO: create a separated function for list (it is extremaly based on html target)
 *
 * @author Hugo
 */
public class OldLmlDocument {

    /** NEW **/
    private StringBuilder output = new StringBuilder();
    private static final char boldChar = '*';
    private static final char italicChar = '/';
    private static final char underlineChar = '_';
    private static final char strikeChar = '+';
    private static final char supChar = '^';
    private static final char subChar = '~';
    private static final char notChar = '@';
    private static final char commentChar = '%';
    private static final char preChar = '&';
    private static final char chapterChar = '=';
    private static final char paragraphChar = '$';
    private static final char listChar = '#';
    private static final char escapeChar = '\\';
    private static final char linefeedChar = '\n';
    private static String header, tail;
    private static final int bold = 0;
    private static final int italic = 1;
    private static final int underline = 2;
    private static final int strike = 3;
    private static final int sup = 4;
    private static final int sub = 5;
    private static final int code = 6;
    private static final int chapter = 7;
    private static final int subchapter = 8;
    private static final int section = 9;
    private static final int subsection = 10;
    private static final int paragraph = 11;
    private static final int linebreak = 12;
    private static final int list = 13;
    private static final int itemlist = 14;
    private targetTypes target;

    public static enum marks {

        bold, italic, underline, strike, sup, sub, pre, not, comment
    }

    public static enum tokens {

        commentLine, commentBlock, keyword, oper
    }
    private ArrayList<String[]> tags = new ArrayList<String[]>(20);
    // TODO: retirar o 'hard coded' e colocar a opção de escolha da estrutura
    private static final char[] chapterStruct = {'1', '.', '1', '.', '1', '.', '1'};
    private static int chapA = 0;
    private static int chapB = 0;
    private static int chapC = 0;
    private static int chapD = 0;

    public static enum targetTypes {

        txt, html
    }

    public OldLmlDocument(targetTypes target) {

        this.target = target;
        initTags();
    }

    public static boolean isOper(String s) {
        if (s.length() != 1) {
            return false;
        }
        if ((s.equals(String.valueOf(boldChar))) || (s.equals(String.valueOf(italicChar))) || (s.equals(String.valueOf(underlineChar))) ||
                (s.equals(String.valueOf(strikeChar))) || (s.equals(String.valueOf(supChar))) || (s.equals(String.valueOf(subChar))) ||
                (s.equals(String.valueOf(notChar))) || (s.equals(String.valueOf(preChar)))) {
            return true;
        }
        return false;
    }

    public static String getMark(marks m) {
        switch (m) {
            case bold:
                return String.valueOf(boldChar);
            case italic:
                return String.valueOf(italicChar);
            case underline:
                return String.valueOf(underlineChar);
            case strike:
                return String.valueOf(strikeChar);
            case sup:
                return String.valueOf(supChar);
            case sub:
                return String.valueOf(subChar);
            case pre:
                return String.valueOf(preChar);
            case not:
                return String.valueOf(notChar);
            case comment:
                return String.valueOf(commentChar);
            default:
                return "";
        }
    }

    public void setTarget(targetTypes target) {
        this.target = target;
        initTags();
    }

    private void initTags() {
        switch (target) {
            case html:
                header = "<html><head><title></title></head><body>";
                tail = "</body></html>";
                tags.add(bold, new String[]{"<b>", "</b>"});
                tags.add(italic, new String[]{"<i>", "</i>"});
                tags.add(underline, new String[]{"<u>", "</u>"});
                tags.add(strike, new String[]{"<s>", "</s>"});
                tags.add(sup, new String[]{"<sup>", "</sup>"});
                tags.add(sub, new String[]{"<sub>", "</sub>"});
                tags.add(code, new String[]{"<code>", "</code>"});
                tags.add(chapter, new String[]{"<h1>", "</h1>"});
                tags.add(subchapter, new String[]{"<h2>", "</h2>"});
                tags.add(section, new String[]{"<h3>", "</h3>"});
                tags.add(subsection, new String[]{"<h4>", "</h4>"});
                tags.add(paragraph, new String[]{"<p class=", "</p>"});
                tags.add(linebreak, new String[]{"<br />", "<br />"});
                tags.add(list, new String[]{"<ul>", "</ul>"});
                tags.add(itemlist, new String[]{"<li>", "</li>"});

                break;
        }
    }

    public StringBuilder parse(String input) {
        return parse(new StringBuilder(input));
    }

    public StringBuilder parse(StringBuilder input) {

        if (input == null || input.length() == 0) {
            return new StringBuilder("");
        }

        boolean isBold, isItalic, isUnderline, isStrike, isSup, isSub, isCode, isParagraph, isComment, isPre, isList;
        isBold = isItalic = isUnderline = isStrike = isSup = isSub = isCode = isParagraph = isComment = isPre = isList = false;

        int chapMarkerCount, chapTag = 0;
        chapA = chapB = chapC = chapD = 0;
        boolean chapStarted = false;

        int listMarkerCount, listCount = 1;
        StringBuilder listStyle = new StringBuilder();

        // delete the old content
        output.delete(0, output.length());
        // append the header
        output.append(header);

        // adjustments for loop
        char previous = '.';
        char current = input.charAt(0);
        char next = '.';
        input.append("\n\n\n\n");

        for (int i = 0; i < (input.length() - 2); i++, previous = current, current = input.charAt(i), next = input.charAt(i + 1)) {

            /*** comment - middle ***/
            if (isComment && current != commentChar) {
                continue;
            } // comment - start and end
            else if (current == commentChar) {
                isComment = !isComment;
                continue;
            }
            // pre - middle
            if (isPre && current != preChar) {
                output.append(current);
                continue;
            } // pre - start and end
            else if (current == preChar) {
                isPre = !isPre;
                continue;
            }
            // escape char \
            if (current == escapeChar && next != ' ') {
                output.append(next);
                i++;
                continue;
            }
            if (previous == ' ' && current == commentChar && next == ' ') {
                isComment = true;
                continue;
            }
            // normal char - not a marker
            if (previous == ' ' && next == ' ' || !isMarker(current)) {
                output.append(current);
                continue;
            }

            // if the target is txt, do not proccess any marker
            if (target == targetTypes.txt) {
                continue;
            }

            switch (current) {
                case boldChar:
                    if (isBold) {
                        output.append(tags.get(bold)[1]);
                    } else {
                        output.append(tags.get(bold)[0]);
                    }
                    isBold = !isBold;
                    break;
                case italicChar:
                    if (isItalic) {
                        output.append(tags.get(italic)[1]);
                    } else {
                        output.append(tags.get(italic)[0]);
                    }
                    isItalic = !isItalic;
                    break;
                case underlineChar:
                    if (isUnderline) {
                        output.append(tags.get(underline)[1]);
                    } else {
                        output.append(tags.get(underline)[0]);
                    }
                    isUnderline = !isUnderline;
                    break;
                case strikeChar:
                    if (isStrike) {
                        output.append(tags.get(strike)[1]);
                    } else {
                        output.append(tags.get(strike)[0]);
                    }
                    isStrike = !isStrike;
                    break;
                case supChar:
                    if (isSup) {
                        output.append(tags.get(sup)[1]);
                    } else {
                        output.append(tags.get(sup)[0]);
                    }
                    isSup = !isSup;
                    break;
                case subChar:
                    if (isSub) {
                        output.append(tags.get(sub)[1]);
                    } else {
                        output.append(tags.get(sub)[0]);
                    }
                    isSub = !isSub;
                    break;
                case notChar:
                    if (isCode) {
                        output.append(tags.get(code)[1]);
                    } else {
                        output.append(tags.get(code)[0]);
                    }
                    isCode = !isCode;
                    break;
                //..: list
                case listChar:
                    if (!isList) {
                        output.append(tags.get(list)[0]);
                        isList = true;
                        // verify if there is a type of list (if it is not use default) -- strong html
                        switch (next) {
                            case 'a':
                                listStyle.append(" style=\"list-style: lower-alpha outside;\"");
                                i++;
                                break;
                            case 'A':
                                listStyle.append(" style=\"list-style: upper-alpha outside;\"");
                                i++;
                                break;
                            case 'i':
                                listStyle.append(" style=\"list-style: lower-roman outside;\"");
                                i++;
                                break;
                            case 'I':
                                listStyle.append(" style=\"list-style: upper-roman outside;\"");
                                i++;
                                break;
                            case '1':
                                listStyle.append(" style=\"list-style: decimal outside;\"");
                                i++;
                                break;
                            case 'd':
                                listStyle.append(" style=\"list-style: disk outside;\"");
                                i++;
                                break;
                            case 'c':
                                listStyle.append(" style=\"list-style: circle outside;\"");
                                i++;
                                break;
                            case 's':
                                listStyle.append(" style=\"list-style: square outside;\"");
                                i++;
                                break;
                            default:
                                listStyle.append(" style=\"list-style: disk outside;\"");
                                break;
                        }
                        // inserts before the '>'
                        output.insert(output.length() - 1, listStyle);
                        output.append(tags.get(itemlist)[0]);
                    } else {
                        // counts the markers
                        listMarkerCount = 0;
                        do {
                            i++;
                            listMarkerCount++;
                        } while (input.charAt(i) == listChar);
                        // creates the open tags
                        if (listCount < listMarkerCount) {
                            for (int j = listCount; j < listMarkerCount; j++) {
                                output.append(tags.get(list)[0]);
                                output.insert(output.length() - 1, listStyle);
                            }
                        } // creates the close tags
                        else if (listCount > listMarkerCount) {
                            for (int j = listMarkerCount; j < listCount; j++) {
                                output.append(tags.get(list)[1]);
                            }
                        }
                        listCount = listMarkerCount;
                        output.append(tags.get(itemlist)[0]);
                    }
                    break;
                //..: chapter
                case chapterChar:
                    // if the marker is not the first character in the line (and it is not the first character) it is not a valid chapter marker
                    if (previous != linefeedChar && i != 0) {
                        output.append(current);
                        continue;
                    }
                    chapMarkerCount = 0;
                    // the test "< 4" is for the case of more than 4 ='s
                    do {
                        i++;
                        chapMarkerCount++;
                    } while (input.charAt(i) == chapterChar && chapMarkerCount < 4);
                    switch (chapMarkerCount) {
                        case 1:
                            chapTag = chapter;
                            chapA++;
                            chapB = chapC = chapD = 0;
                            break;
                        case 2:
                            chapTag = subchapter;
                            chapB++;
                            chapC = chapD = 0;
                            break;
                        case 3:
                            chapTag = section;
                            chapC++;
                            chapD = 0;
                            break;
                        case 4:
                            chapTag = subsection;
                            chapD++;
                            break;
                    }
                    // get the tags and markers [1.1.1]
                    output.append(tags.get(chapTag)[0]);
                    output.append(getChapterString());
                    chapStarted = false;
                    // include the rest of line
                    while (current != linefeedChar) {
                        if (current != ' ' && current != chapterChar) {
                            chapStarted = true;
                        }
                        if (chapStarted) {
                            output.append(current);
                        }
                        i++;
                        current = input.charAt(i);
                    }
                    output.append(tags.get(chapTag)[1]);
                    // include a linefeed for organization
                    output.append(linefeedChar);
                    break;
                //..: paragraph
                case paragraphChar:
                    // if the marker is not the first character in the line it is not a valid paragraph marker
                    if (previous != linefeedChar) {
                        output.append(current);
                        continue;
                    }
                    // set the paragraph as started
                    isParagraph = true;
                    output.append(tags.get(paragraph)[0]);
                    // get class - all targets or just to html?
                    output.append(getParagraphClass(input.charAt(i + 1), input.charAt(i + 2)));
                    // set the cursor to the correct position
                    i += 3;
                    break;
                //..: linefeed
                case linefeedChar:
                    // closing paragraph
                    if (next == linefeedChar && isParagraph) {
                        isParagraph = false;
                        output.append(tags.get(paragraph)[1]);
                        output.append(linefeedChar);
                        // ignore the next linefeed character which is used just for code organization
                        i++;
                    } // append a breakline
                    else if (isParagraph) {
                        output.append(tags.get(linebreak)[0]);
                        output.append(linefeedChar);
                    } // closing a item list
                    else if (next == listChar) {
                        output.append(tags.get(itemlist)[1]);
                    } // closing a list
                    else if (next == linefeedChar && isList) {
                        isList = false;
                        output.append(tags.get(itemlist)[1]);
                        while (listCount > 0) {
                            output.append(tags.get(list)[1]);
                            listCount--;
                        }
                    } else {
                        output.append(current);
                    }
                    break;
            }
        }
        // append the tail
        return output.append(tail);
    }

    private static StringBuilder getChapterString() {
        StringBuilder res = new StringBuilder();
        switch (chapterStruct[0]) {
            case '1':
                res.append(chapA);
                break;
            case 'A':
                res.append((char) (chapA - 1 + 'A'));
                break;
            case 'a':
                res.append((char) (chapA - 1 + 'a'));
                break;
            case 'I':
                res.append(Utils.getRomanNumeral(chapA, true));
                break;
            case 'i':
                res.append(Utils.getRomanNumeral(chapA, false));
                break;
        }
        res.append(chapterStruct[1]);
        if (chapB == 0) {
            return res.append(' ');
        }
        switch (chapterStruct[2]) {
            case '1':
                res.append(chapB);
                break;
            case 'A':
                res.append((char) (chapB - 1 + 'A'));
                break;
            case 'a':
                res.append((char) (chapB - 1 + 'a'));
                break;
            case 'I':
                res.append(Utils.getRomanNumeral(chapB, true));
                break;
            case 'i':
                res.append(Utils.getRomanNumeral(chapB, false));
                break;
        }
        res.append(chapterStruct[3]);
        if (chapC == 0) {
            return res.append(' ');
        }
        switch (chapterStruct[4]) {
            case '1':
                res.append(chapC);
                break;
            case 'A':
                res.append((char) (chapC - 1 + 'A'));
                break;
            case 'a':
                res.append((char) (chapC - 1 + 'a'));
                break;
            case 'I':
                res.append(Utils.getRomanNumeral(chapC, true));
                break;
            case 'i':
                res.append(Utils.getRomanNumeral(chapC, false));
                break;
        }
        res.append(chapterStruct[5]);
        if (chapD == 0) {
            return res.append(' ');
        }
        switch (chapterStruct[6]) {
            case '1':
                res.append(chapD);
                break;
            case 'A':
                res.append((char) (chapD - 1 + 'A'));
                break;
            case 'a':
                res.append((char) (chapD - 1 + 'a'));
                break;
            case 'I':
                res.append(Utils.getRomanNumeral(chapD, true));
                break;
            case 'i':
                res.append(Utils.getRomanNumeral(chapD, false));
                break;
        }
        res.append(' ');
        return res;
    }

    private StringBuilder getParagraphClass(char x, char y) {
        StringBuilder res = new StringBuilder();
        if (x == 'b' && y == 't') {
            res.append("\"BodyText\"");
        } else if (x == 'm' && y == 't') {
            res.append("\"MonoText\"");
        } else if (x == 'c' && y == 't') {
            res.append("\"CitationText\"");
        } else if (x == 'i' && y == 't') {
            res.append("\"InformationText\"");
        } // Unknown parameters
        else {
            return res;
        }
        if (target == targetTypes.html) {
            res.append(">");
        }
        return res;
    }

    /**
     * Returns true if the char is a marker
     * @param c - the char
     * @return true if is a marker
     */
    private static boolean isMarker(char c) {
        return (c == chapterChar || c == paragraphChar || c == listChar || c == boldChar ||
                c == italicChar || c == underlineChar || c == strikeChar || c == supChar ||
                c == subChar || c == notChar || c == commentChar || c == preChar ||
                c == linefeedChar || c == '|' || c == '>' || c == '<');
    }

    public static void main(String[] args) {

        /*
        StringBuffer st = new StringBuffer();
        int ind;
        long l = new Date().getTime();
        for(int i = 0; i < 1000; i++) {
        ind = (int) Math.floor(Math.random() * st.length());
        st.append(" 42 " + ind);
        }
        System.out.println(new Date().getTime() - l);
         */

        String x = "= Dirwyne, o grande estrategista\n" +
                "== Subtitle\n" +
                "$bt No ano 352, Dirfyne foi encarregado de executar o transportes de mercadorias entre Ruyeg e Towei. Exerceu seu papel de uma forma eficiente apesar de não ser um cargo apropriado para uma elfo de tão alta estirpe. Sua família ostentava a fama de ter sido a fundadora do reino e, por isso mesmo era mal vista por Itrick, o rei.\n\n" +
                "$bt Ao voltar de sua viagem, o rei o acusou de trair o reino e compactuar com bárbaros anões, nessa época já eram visíveis os embates entre as duas raças.\n\n" +
                "$bt Dirfyne foi expulso do reino sem direito a se defender e jurou vingança.\n\n" +
                "$bt Através dele se tornou aberto o combate entre elfos e anões, graças a ataques que empreendia ao barbudos sob o nome do rei.\n\n" +
                "$bt Teve paciência e dedicou seu tempo seguinte a transformar seu filho, Dirwyne, que teve com a irmã de Itrick que foi raptada, em um grande militar. Ensinou-lhe as artes da guerra e da estratégia, aspecto em que superou o pai. Nutria a seus semelhantes desprezo igual ao pai.\n\n" +
                "$bt Dirwyne não teve o menor pudor em recrutar para seu exército triits e orcs. E quando Ruyeg já não podia resistir a um ataque devido ao desgaste ocasionado pela guerra lançou o ataque. Ruyeg estava destruída, os elfos dispersos, seu pai havia sido honrado.\n\n" +
                "$bt Sua ambição, porém foi mais forte que seu intelecto, e Dirwyne marchou rumo a Towei, onde foi derrotado.\n\n" +
                "$bt Agora, seu orgulho recusava a deixar que seres inferiores o desafiassem. Mais uma vez deixou de lado seus preconceitos e se aliou a Feond, o chefe de uma grande tribo humana bárbara.\n\n" +
                "$bt Com este novo aliado, Dirwyne teve mais êxito e já havia tomado todo o território do reino, menos da cidade real. De repente, a maré da batalha virou com a ajuda de elfos, que tinham se reunido e dungs, poderosos magos.\n\n" +
                "$bt O exército de Dirwyne foi vencido e este se suicidou antes de ser capturado.\n\n" +
                "$it possivelmente válido, embora possa e deva ser melhor trabalhado\n\n" +
                "$it fim do texto, ínicio dos testes\n" +
                "$bt Exemplos: *bold* \n" +
                "/italic/ \n" +
                "_underline_\n" +
                "+strike+ \n" +
                "^super^script \n" +
                "~sub~script \n" +
                "@pre@ \n" +
                "&n*o*t& \n" +
                "comment? %not% true\n\n" +
                "#a item 1\n" +
                "## item 1.1\n" +
                "## item 1.2\n" +
                "# item 2\n\n" +
                "$bt falta colocar tables, etc  ";

        StringBuilder o, s = new StringBuilder(x);
        OldLmlDocument doc = new OldLmlDocument(OldLmlDocument.targetTypes.html);

        long l = new Date().getTime();
        o = doc.parse(s);
        long l2 = new Date().getTime();
        System.out.println("Result :: " + (l2 - l) + " milliseconds");
        System.out.println(o);

        /*

        StringBuffer s = new StringBuffer(x);
        long l = new Date().getTime();

        UniDocument.convert(s, UniDocument.TYPE_HTML);
        long l2 = new Date().getTime();
        System.out.println("Result :: " + (l2 - l) + " milliseconds");
        System.out.println(output2);
         *
         * */

        //String test = "^teste^2\n= Teste\n== Teste2\n\n=== Teste3\n==== Teste4\n===== Teste5\n\n\n$bt teasfdsafsdasasd adfkjsdfk hsdfjhsdf sdkjh dsjksdhdsf \\ \n\n\n$mt testes saasd\n\n\n\n\n\n";
        //UniDocument.convert(new StringBuffer(test), UniDocument.TYPE_HTML);
        //System.out.println("[" + test + "]\n\n" + output.toString());

    }
}
